/*
 * Copyright 2009 Hao Nguyen
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package tetrinet.client;

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.user.client.Random;
import com.google.gwt.user.client.Window;

/**
 * Stores the information about each block in tetris.
 * 
 * @author hao1300@gmail.com
 */
public class TetrisMatrix {	
	private final BlockType[] blocks;
	private final int numRows, numCols;	

	public TetrisMatrix(int numRows, int numCols) {
		this.numRows = numRows;
		this.numCols = numCols;
		this.blocks = new BlockType[numRows * numCols];
	}
	
	/**
	 * Gets the number of rows in the matrix.
	 */
	public int getNumRows() {
		return numRows;
	}
	
	/**
	 * Gets the number of columns in the matrix.
	 */
	public int getNumCols() {
		return numCols;
	}
	
	/**
	 * Gets the information about the block.
	 * 	
	 * @param row
	 * @param col
	 * @return the type of block at the given cell.
	 */
	public BlockType getBlock(int row, int col) {
		return blocks[row * numCols + col];
	}
	
	/**
	 * Sets the information about the block.
	 * 
	 * @param row
	 * @param col
	 * @param type type of block at the given cell to set to.
	 */
	public void setBlock(int row, int col, BlockType type) {
		blocks[row * numCols + col] = type;
	}
	
	/**
	 * Checks whether the given piece can fit into the given top-left corner.
	 * 
	 * @param row
	 * @param col
	 * @param piece
	 * @return true if the piece can fit into the given position.
	 */
	public boolean isValidPiece(int row, int col, Piece piece) {
		for (int r = 0; r < Piece.PIECE_SIZE; r++) {
			for (int c = 0; c < Piece.PIECE_SIZE; c++) {
				BlockType type = piece.getBlock(r, c);
				if (type == null) {
					continue;
				}
				int tempRow = r + row;
				if (tempRow < 0 || tempRow >= numRows) {
					return false;
				}
				int tempCol = c + col;
				if (tempCol < 0 || tempCol >= numCols) {
					return false;
				}
				if (getBlock(tempRow, tempCol) != null) {
					return false;
				}
			}
		}
		return true;
	}
	
	/**
	 * Sets the piece into the given top-left position.
	 * 
	 * @param row
	 * @param col
	 * @param piece
	 */
	public void setPiece(int row, int col, Piece piece) {
		for (int r = 0; r < Piece.PIECE_SIZE; r++) {
			for (int c = 0; c < Piece.PIECE_SIZE; c++) {
				BlockType type = piece.getBlock(r, c);
				if (type == null) {
					continue;
				}
				setBlock(r + row, c + col, type);
			}
		}
	}
	
	/**
	 * Removes the piece into the given top-left position.
	 * 
	 * @param row
	 * @param col
	 * @param piece
	 */
	public void removePiece(int row, int col, Piece piece) {
		for (int r = 0; r < Piece.PIECE_SIZE; r++) {
			for (int c = 0; c < Piece.PIECE_SIZE; c++) {
				if (piece.getBlock(r, c) == null) {
					continue;
				}
				setBlock(r + row, c + col, null);
			}
		}
	}
	
	/**
	 * Checks and clears any filled row above the given row. 
	 * 
	 * @param row the row above which is to be checked and cleared.
	 * @returns the number of rows cleared.
	 */
	public Tuple2<Integer,List<BlockType>> checkAndClear(int row) {
		row = Math.min(row, numRows - 1);
		
		// Find the cleared rows.
		List<Integer> clearedRows = new ArrayList<Integer>(); 
		for (int r = row; r >= row - Piece.PIECE_SIZE; r--) {
			boolean isRowCleared = true;
			for (int c = 0; c < numCols; c++) {
				if (getBlock(r, c) == null) {
					isRowCleared = false;
					break;
				}
			}
			if (isRowCleared) {
				clearedRows.add(r);
			}
		}
		
		if (clearedRows.isEmpty()) {
			return new Tuple2<Integer,List<BlockType>>(0,null);
		}
		
		List<BlockType> specialBlocksAcquired = new ArrayList<BlockType>(); 
		
		for( int r : clearedRows){
			for (int c = 0; c < numCols; c++) {
				BlockType b = getBlock(r,c);
				if( b != null ) {
					for( int j = 0 ; j < specialBlocks.length ; j++ ) {
						if( specialBlocks[j] == b) {
							specialBlocksAcquired.add(b);
						}
					}
				}
			}
		}
		
		// Moves the rows down to fill up the cleared rows.
		for (int i = 0, toRow = clearedRows.get(i), fromRow = toRow - 1; 
				toRow >= 0; fromRow--) {
			if ((i < clearedRows.size() - 1) 
					&& (fromRow == clearedRows.get(i + 1))) {
				i++;
				continue;
			}
			
			for (int c = 0; c < numCols; c++) {
				setBlock(toRow, c, (fromRow < 0) ? null : getBlock(fromRow, c));
			}
			toRow--;
		}
		return new Tuple2<Integer,List<BlockType>>(clearedRows.size(),specialBlocksAcquired);
	}

	private final BlockType[] specialBlocks = new BlockType[] {
			BlockType.ADD_LINE,
			BlockType.REMOVE_SPECIAL_BLOCKS,
			BlockType.CLEAR_LINE,
			BlockType.GRAVITY,
			BlockType.BLOCK_QUAKE,
			BlockType.RANDOM_BLOCK_CLEAR,
			BlockType.SWITCH_FIELDS,
			BlockType.BLOCK_BOMB,
			BlockType.NUKE_FIELD};
	
	public void addSpecialBlocks(int num) {
		List<Integer> b = new ArrayList<Integer>();
		for(int i = blocks.length-1 ; i >=0 ; i-- ) {
			if( blocks[i] != null)
			{
				b.add(i);
			}
		}
		
		if( b.size() > 0 ) {
			for(int i = 0 ; i < num ; i++) {
				int j = Random.nextInt(b.size());
				int k = b.get(j);
				blocks[k] = specialBlocks[Random.nextInt(specialBlocks.length)];
			}
		}
	}
	
	public String getAsString(){
		String s = "";
		for(BlockType b : blocks) {
			if(b == null ) {
				s += " ";
			}
			else {
				s += b.getBlockSymbol();
			}
		}
		return s;
	}

	public void updateFromString(String s) {
		for( int i = 0 ; i < s.length(); i++) {
			blocks[i] = BlockType.getByString(s.substring(i,i+1));
		}
	}

	public void clearBoard() {
		for( int i = 0 ; i < blocks.length; i++) {
			blocks[i] = null;
		}
	}

	public BlockType getRandomBlockType() {
		
		int i = Random.nextInt(6);
		if( i == 0) {
			return BlockType.SHAPE_I;
		}
		else if( i == 1) {
			return BlockType.SHAPE_J;
		}
		else if( i == 2) {
			return BlockType.SHAPE_O;
		}
		else if( i == 3) {
			return BlockType.SHAPE_S;
		}
		else if( i == 4) {
			return BlockType.SHAPE_T;
		}
		else  {
			return null;
		}
	}
	
public BlockType getRandomBlockTypeNoNull() {
		
		int i = Random.nextInt(5);
		if( i == 0) {
			return BlockType.SHAPE_I;
		}
		else if( i == 1) {
			return BlockType.SHAPE_J;
		}
		else if( i == 2) {
			return BlockType.SHAPE_O;
		}
		else if( i == 3) {
			return BlockType.SHAPE_S;
		}
		else  {
			return BlockType.SHAPE_T;
		}
	}
	
	public boolean isSpecialBlock(BlockType special_block) {
		if(special_block == BlockType.ADD_LINE || special_block == BlockType.REMOVE_SPECIAL_BLOCKS || special_block == BlockType.CLEAR_LINE || special_block == BlockType.GRAVITY || special_block == BlockType.BLOCK_QUAKE || special_block == BlockType.RANDOM_BLOCK_CLEAR || special_block == BlockType.SWITCH_FIELDS || special_block == BlockType.BLOCK_BOMB || special_block == BlockType.NUKE_FIELD){
			return true;
		}
		return false;
	}
	
	public void addLine() {
		for (int i = 0; i < numRows; i++) {
			for (int c = 0; c < numCols; c++) {
				setBlock(i, c, (i+1 >= numRows) ? getRandomBlockType() : getBlock(i+1, c));
			}
		}
	}

	public void removeSpecialBlocks() {
		for( int i = 0 ; i < blocks.length; i++) {
			if( isSpecialBlock(blocks[i]) ) {
				blocks[i] = getRandomBlockTypeNoNull();
			}
		}
	}

	public void clearLine() {
		for (int i = 0, toRow = numRows-1, fromRow = toRow - 1;  toRow >= 0; fromRow--) {
			
			for (int c = 0; c < numCols; c++) {
				setBlock(toRow, c, (fromRow < 0) ? null : getBlock(fromRow, c));
			}
			toRow--;
		}
	}

	public void blockGravity() {
		for (int c = 0; c < numCols; c++) {
			List<BlockType> b = new ArrayList<BlockType>();
			for (int r = numRows-1; r >= 0; r--) {
				BlockType t = getBlock(r,c);
				if( t != null) {
					b.add(t);
					setBlock(r,c, null);
				}
			}
			for (int r = numRows-1; r >= 0; r--) {
				int i = numRows-r-1;
				if(i < b.size()){
					setBlock(r,c, b.get(i));
				}
				else{
					break;
				}
			}
		}
	}

	public void blockQuake() {
		for (int c = 0; c < numCols; c++) {
			for (int r = numRows-1; r >= 0; r--) {
				BlockType t = getBlock(r,c);
				if( t != null) {
					if(Random.nextDouble()<.2) {
						
						if(Random.nextDouble()<.5 && c-1 > 0 && getBlock(r,c-1) == null) {
							setBlock(r,c,null);
							setBlock(r,c-1,t);
						}
						else if(  c+1 < numCols && getBlock(r,c+1) == null){
							setBlock(r,c,null);
							setBlock(r,c+1,t);
						}
					}
				}
			}
		}
	}

	public void randomBlockClear() {
		for( int i = 0 ; i < blocks.length; i++) {
			if( Random.nextDouble() < 0.1 ) {
				blocks[i] = null;
			}
		}
	}

	public void blockBomb() {
		for (int c = 0; c < numCols; c++) {
			for (int r = numRows-1; r >= 0; r--) {
				BlockType b = getBlock(r,c);
				if(b == BlockType.BLOCK_BOMB) {
					setBlock(r,c,null);
					explodeLocation(r+1,c-1);
					explodeLocation(r+1,c);
					explodeLocation(r+1,c+1);
					explodeLocation(r,c-1);
					
					explodeLocation(r,c+1);
					explodeLocation(r-1,c-1);
					explodeLocation(r-1,c);
					explodeLocation(r-1,c+1);
				}
			}
		}
	}
	
	public void explodeLocation(int r, int c) {
		if(r >= 0 && r < numRows && c >= 0 && c < numCols) {
			BlockType b = getBlock(r,c);
			setBlock(r,c,null);
			blocks[Random.nextInt(blocks.length)] = b;
		}
	}

	public void nukeField() {
		clearBoard();
	}
}